package edu.udo.sopra10.chaturaji.ai;

import java.util.ArrayList;
import java.util.LinkedList;

import org.apache.commons.lang.NotImplementedException;

import edu.udo.sopra10.chaturaji.controllers.GameController;
import edu.udo.sopra10.chaturaji.entities.Player;
import edu.udo.sopra10.chaturaji.entities.Square;
import edu.udo.sopra10.chaturaji.entities.coordinates.Position;
import edu.udo.sopra10.chaturaji.entities.pieces.Boat;
import edu.udo.sopra10.chaturaji.entities.pieces.Elephant;
import edu.udo.sopra10.chaturaji.entities.pieces.Horse;
import edu.udo.sopra10.chaturaji.entities.pieces.King;
import edu.udo.sopra10.chaturaji.entities.pieces.Pawn;
import edu.udo.sopra10.chaturaji.entities.pieces.Piece;
import edu.udo.sopra10.chaturaji.entities.transfer.DiceRoll;
import edu.udo.sopra10.chaturaji.entities.transfer.MoveRequest;
import edu.udo.sopra10.chaturaji.entities.transfer.TransferGame;
import edu.udo.sopra10.chaturaji.entities.transfer.DiceRoll.EquivalenceClass;
import edu.udo.sopra10.chaturaji.events.BoardResetEvent;
import edu.udo.sopra10.chaturaji.events.MoveRequestEvent;
import edu.udo.sopra10.chaturaji.events.TurnRequestEvent;
import edu.udo.sopra10.chaturaji.exceptions.NoPieceException;

public class HardAI extends AbstractAI {

	private class ReturnPieceAndPositionAndValue {
		private Position position;
		private Piece piece;
		private int value;

		ReturnPieceAndPositionAndValue(Piece piece, Position position, int value) {
			this.position = position;
			this.piece = piece;
			this.value = value;
		}

		public Piece getPiece() {
			return piece;
		}

		public Position getPosition() {
			return position;
		}

		public int getValue() {
			return value;
		}
	}

	private class ReturnDiceOrder {
		private ReturnPieceAndPositionAndValue diceOne;
		private ReturnPieceAndPositionAndValue diceTwo;

		public ReturnDiceOrder(ReturnPieceAndPositionAndValue diceOne,
				ReturnPieceAndPositionAndValue diceTwo) {
			this.diceOne = diceOne;
			this.diceTwo = diceTwo;
		}

		public ReturnPieceAndPositionAndValue getDiceOne() {
			return diceOne;
		}

		public ReturnPieceAndPositionAndValue getDiceTwo() {
			return diceTwo;
		}
	}

	private boolean diceOrderDecided;
	ReturnDiceOrder diceOrder;

	public HardAI() {
		GameController.addMoveRequestEventListener(this);
		GameController.addBoardResetEventListener(this);
		GameController.addMoveEventListener(this);
		GameController.addTurnRequestEventListener(this);
	}

	private ReturnDiceOrder evaluateDiceOrder(EquivalenceClass diceOne,
			EquivalenceClass diceTwo) {
		
		Square saveSquare = null;
		boolean saveOne = false;
		boolean saveTwo = false;

		ReturnPieceAndPositionAndValue diceOrderOneTwoA = evaluateDice(diceOne);
		if(diceOrderOneTwoA != null){
			board[diceOrderOneTwoA.getPiece().getSquare().getPosition().getX()][diceOrderOneTwoA.getPiece().getSquare().getPosition().getY()].setPiece( null );
			board[diceOrderOneTwoA.getPosition().getX()][diceOrderOneTwoA.getPosition().getY()].setPiece(diceOrderOneTwoA.getPiece());
			saveSquare = diceOrderOneTwoA.getPiece().getSquare();
			diceOrderOneTwoA.getPiece().setSquare(board[diceOrderOneTwoA.getPosition().getX()][diceOrderOneTwoA.getPosition().getY()]);
			saveOne = true;
//			System.out.print("EvalDiceorder: ");
//			System.out.println(diceOrderOneTwoA.getPiece());
		}
		ReturnPieceAndPositionAndValue diceOrderOneTwoB = evaluateDice(diceTwo);
		
		if(saveOne) diceOrderOneTwoA.getPiece().setSquare(saveSquare);
		
		game = new TransferGame(GameController.getGame());
		board = game.getBoard();		

		ReturnPieceAndPositionAndValue diceOrderTwoOneA = evaluateDice(diceTwo);
		if(diceOrderTwoOneA != null){
			board[diceOrderTwoOneA.getPiece().getSquare().getPosition().getX()][diceOrderTwoOneA.getPiece().getSquare().getPosition().getY()].setPiece( null );
			board[diceOrderTwoOneA.getPosition().getX()][diceOrderTwoOneA.getPosition().getY()].setPiece(diceOrderTwoOneA.getPiece());
			saveSquare = diceOrderTwoOneA.getPiece().getSquare();
			diceOrderTwoOneA.getPiece().setSquare(board[diceOrderTwoOneA.getPosition().getX()][diceOrderTwoOneA.getPosition().getY()]);
			saveTwo = true;
		}
		ReturnPieceAndPositionAndValue diceOrderTwoOneB = evaluateDice(diceOne);

		if(saveTwo)diceOrderTwoOneA.getPiece().setSquare(saveSquare);
		
		if ((diceOrderOneTwoA == null && diceOrderOneTwoB == null)
				&& (diceOrderTwoOneA == null && diceOrderTwoOneB == null))
			return new ReturnDiceOrder(null, null);
		
		if( (diceOrderOneTwoA == null || diceOrderOneTwoB == null) && (diceOrderTwoOneA != null && diceOrderTwoOneB != null))
			return new ReturnDiceOrder(diceOrderTwoOneA, diceOrderTwoOneB);
		
		if( (diceOrderOneTwoA != null && diceOrderOneTwoB != null) && (diceOrderTwoOneA == null || diceOrderTwoOneB == null))
			return new ReturnDiceOrder(diceOrderOneTwoA, diceOrderOneTwoB);
		
		if (diceOrderOneTwoA == null && diceOrderOneTwoB != null)
			return new ReturnDiceOrder(diceOrderOneTwoB, null);
		
		if (diceOrderOneTwoA != null && diceOrderOneTwoB == null)
			return new ReturnDiceOrder(diceOrderOneTwoA, null);

		if ((diceOrderOneTwoA.getValue() + diceOrderOneTwoB.getValue()) > (diceOrderTwoOneA
				.getValue() + diceOrderTwoOneB.getValue()))
			return new ReturnDiceOrder(diceOrderOneTwoA, diceOrderOneTwoB);
		else
			return new ReturnDiceOrder(diceOrderTwoOneA, diceOrderTwoOneB);
	}

	private ReturnPieceAndPositionAndValue evaluateDice(EquivalenceClass dice) {
		try {
			Piece piece = null;
			Position bestPosition = null;
			LinkedList<Piece> pieces = new LinkedList<Piece>();
			ReturnPieceAndPositionAndValue obj = null;
			int bestPositionValue = 0;

			switch (dice) {
			case ONE_OR_FIVE:
				pieces = getPieces();
				obj = getBestPositionKingPawn(pieces);
				piece = obj.getPiece();
				bestPosition = obj.getPosition();
				bestPositionValue = obj.getValue();
				break;
			case TWO:
				piece = getPiece(Boat.class);
				obj = getBestPosition(piece);
				bestPosition = obj.getPosition();
				bestPositionValue = obj.getValue();
				break;
			case THREE:
				piece = getPiece(Horse.class);
				obj = getBestPosition(piece);
				bestPosition = obj.getPosition();
				bestPositionValue = obj.getValue();
				break;
			case FOUR_OR_SIX:
				piece = getPiece(Elephant.class);
				obj = getBestPosition(piece);
				bestPosition = obj.getPosition();
				bestPositionValue = obj.getValue();
				break;
			}
			if (piece.getSquare().getPosition().equals(bestPosition)) {
				return null;
			}
			return new ReturnPieceAndPositionAndValue(piece, bestPosition,
					bestPositionValue);
		} catch (NoPieceException e) {
			return null;
		}
	}

	protected int evaluatePosition(Piece piece, Position position) {
		int evaluation = 0;
		Position currentPosition = piece.getSquare().getPosition();

		Piece capturedPiece = board[position.getX()][position.getY()]
				.getPiece();
		if (capturedPiece != null
				&& !capturedPiece.getSquare().getPosition()
						.equals(piece.getSquare().getPosition()))
			evaluation += 2 * capturedPiece.getPieceValue();

		board[currentPosition.getX()][currentPosition.getY()].setPiece(null);
		evaluation += 5 * piece.getPossibleActions(board, position)
				.getPossibleMoves().size();
		evaluation += 7 * piece.getPossibleActions(board, position)
				.getPossibleCaptures().size();
		evaluation += 8 - Math.abs(3 - position.getX())
				- Math.abs(4 - position.getY());
		board[currentPosition.getX()][currentPosition.getY()].setPiece(piece);

		for (Piece capturingPiece : getCapturingPieces(position)) {
			if (capturingPiece.getClass() == King.class
					|| capturingPiece.getClass() == Pawn.class
					|| capturingPiece.getClass() == Elephant.class)
				evaluation -= 20;
			else
				evaluation -= 10;
		}

		return evaluation;
	}

	/**
	 * @param pieces
	 * @return
	 */
	private ReturnPieceAndPositionAndValue getBestPositionKingPawn(
			LinkedList<Piece> pieces) {
		Position bestPosition = null;
		int bestValue = 0;
		Piece bestPiece = null;

		for (Piece piece : pieces) {
			// current position
			int positionValue = evaluatePosition(piece, piece.getSquare()
					.getPosition());
			if (positionValue >= bestValue) {
				bestPosition = piece.getSquare().getPosition();
				bestValue = positionValue;
				bestPiece = piece;
			}

			for (Position position : piece.getPossibleActions(board, piece.getSquare().getPosition())
					.getPossibleMoves()) {
				positionValue = evaluatePosition(piece, position);

				if (positionValue >= bestValue) {
					bestPosition = position;
					bestValue = positionValue;
					bestPiece = piece;
				}
			}

			for (Position position : piece.getPossibleActions(board, piece.getSquare().getPosition())
					.getPossibleCaptures()) {
				positionValue = evaluatePosition(piece, position);

				if (positionValue >= bestValue) {
					bestPosition = position;
					bestValue = positionValue;
					bestPiece = piece;
				}
			}
		}

		return new ReturnPieceAndPositionAndValue(bestPiece, bestPosition,
				bestValue);
	}

	/**
	 * @param piece
	 * @return
	 */
	private ReturnPieceAndPositionAndValue getBestPosition(Piece piece) {
//		System.out.print("getBestPos: ");
//		System.out.println(piece);
		Position bestPosition = piece.getSquare().getPosition();
		int bestValue = evaluatePosition(piece, bestPosition);
		int positionValue;

		for (Position position : piece.getPossibleActions(board, piece.getSquare().getPosition()).getPossibleMoves()) {
			positionValue = evaluatePosition(piece, position);
			if (positionValue >= bestValue) {
				bestPosition = position;
				bestValue = positionValue;
			}
		}

		for (Position position : piece.getPossibleActions(board, piece.getSquare().getPosition()).getPossibleCaptures()) {
			positionValue = evaluatePosition(piece, position);
			if (positionValue >= bestValue) {
				bestPosition = position;
				bestValue = positionValue;
			}
		}

		return new ReturnPieceAndPositionAndValue(piece, bestPosition,
				bestValue);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * edu.udo.sopra10.chaturaji.ai.AbstractAI#getCaptureMove(edu.udo.sopra10
	 * .chaturaji.entities.pieces.Piece)
	 */
	@Override
	protected Position getCaptureMove(Piece piece) {
		throw new NotImplementedException();
	}

	private LinkedList<Piece> getPlayerTransferPieces(){
		LinkedList<Piece> pieces = new LinkedList<Piece>();
		for(int i = 0; i <= 7; i++){
			for(int k = 0; k <= 7; k++){
				if(board[i][k].getPiece() != null)
					if(board[i][k].getPiece().getOwner().equals(player))
						pieces.add(board[i][k].getPiece());
			}
		}
		return pieces;
	}
	
	@Override
	public void onMoveRequestEvent(MoveRequestEvent event) {
		if (event.getPlayerType() == Player.Type.AI_HARD) {
			diceOrderDecided = false;

			game = new TransferGame(GameController.getGame());
			board = game.getBoard();
			player = game.getPlayers().get(event.getPlayerId());
			
			pieces = getPlayerTransferPieces();
			
			ArrayList<Byte> dices = event.getUnassignedRolls();
			
//			System.out.println(diceOrderDecided);
				if (!diceOrderDecided && dices.size() == 2) {
					
//					System.out.println(dices.get(0));
//					System.out.println(dices.get(1));
					
					diceOrder = evaluateDiceOrder(
							DiceRoll.equivalenceClassFromRoll(dices.get(0)),
							DiceRoll.equivalenceClassFromRoll(dices.get(1)));
					
					diceOrderDecided = true;
					
					if (diceOrder.getDiceOne() == null){
//						System.out.println("Spiel wird gepasst! 1");
						GameController.pass();
					}
					else{
//						System.out.println("Bewegung 1 wird durchgeführt");
//						System.out.println(diceOrder.getDiceOne().getPiece());
//						System.out.println(diceOrder.getDiceOne().getPosition());
						
						GameController.move(diceOrder.getDiceOne().getPiece()
								.getSquare().getPosition(), diceOrder
								.getDiceOne().getPosition());
					}
				} else {
					if (diceOrder.getDiceTwo() == null){
//						System.out.println("Spiel wird gepasst! 2");
						GameController.pass();
					}
					else{
//						System.out.println("Bewegung 2 wird durchgeführt");
//						System.out.println(diceOrder.getDiceTwo().getPiece());
//						System.out.println(diceOrder.getDiceTwo().getPosition());
						
						try
						{
							GameController.move(diceOrder.getDiceTwo().getPiece()
									.getSquare().getPosition(), diceOrder
									.getDiceTwo().getPosition());
						}
						catch ( NoPieceException e )
						{
							// get hint from HintGiver.
							MoveRequest request = GameController.getHintGiver().getHint( event.getUnassignedRolls(), event.getPlayerId() );
							GameController.move( request.start, request.end );
						}
					}
				}
		}
	}

	@Override
	public void onTurnRequestEvent(TurnRequestEvent event) {
		if (event.getPlayerType() == Player.Type.AI_HARD) {
			player = game.getPlayers().get(event.getPlayerId());
			
			GameController.roll();
		}
	}

	@Override
	public void onBoardResetEvent(BoardResetEvent event) {
		game = new TransferGame(GameController.getGame());
		board = game.getBoard();
	}
}
